home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue45 / Message / SpyEngine.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-11-02  |  16.5 KB  |  576 lines

  1. unit SpyEngine;
  2.  
  3. interface
  4.  
  5. uses
  6.   SysUtils, Classes, Controls, Messages, Windows, ExtCtrls, StdCtrls,
  7.   Forms, DsgnIntf, MessageDict, SpyViewer;
  8.  
  9. type
  10.  
  11.   TFormattedMsgType = (fmtNotApplicable, fmtDispatch, fmtWndProc);
  12.   THookType = (htDispatch, htWndProc, htBoth);
  13.   TMessageType = (mtWindowsMessage, mtCM_Message, mtCN_Message);
  14.   TMessageTypes = set of TMessageType;
  15.  
  16.   TMessageReceiptEvent = procedure(AControl: TControl;
  17.       const Msg: TMessage; HookType: THookType; Lines: TStrings;
  18.       var Filter: Boolean) of Object;
  19.  
  20.   TMessageSpy = class(TCustomPanel)
  21.   private
  22.     FHookee:              TControl;
  23.     FHookType:            THookType;
  24.     FMessageTypes:        TMessageTypes;
  25.     FFilterHeavyHitters:  Boolean;
  26.     FFilterConsecutiveMessages:
  27.                           Boolean;
  28.     FOnMessageReceipt:    TMessageReceiptEvent;
  29.     AButton:              TButton;
  30.     LastMsgDWM:           Word;
  31.     LastMsgDCM:           Word;
  32.     LastMsgDCN:           Word;
  33.     LastMsgWPWM:          Word;
  34.     LastMsgWPCM:          Word;
  35.     LastMsgWPCN:          Word;
  36.     Viewer:               TfrmSpyViewer;
  37.     procedure AButtonClick(Sender: TObject);
  38.     procedure DispatchMessage(const Message);
  39.     function DisplayMessage(HT: THookType; const Message): Boolean;
  40.     function FormattedMessage(MsgType: TFormattedMsgType; const Msg): String;
  41.     procedure HookeeDestructing(Sender: TObject);
  42.     procedure HookerEngaged(Engaged: Boolean);
  43.     function DuplicateMessage(HT: THookType; const Message): Boolean;
  44.     procedure ViewerClosing(Sender: TObject; var Action: TCloseAction);
  45.     procedure WndProcMessage(const Message: TMessage);
  46.   public
  47.     constructor Create(AOwner: TComponent); override;
  48.     destructor Destroy; override;
  49.     procedure SetHookActive(Active: Boolean);
  50.     property Hookee: TControl read FHookee write FHookee;
  51.   published
  52.     property HookType: THookType read FHookType write FHookType
  53.         default htWndProc;
  54.     property MessageTypes: TMessageTypes read FMessageTypes
  55.         write FMessageTypes default [mtCM_Message, mtCN_Message];
  56.     property FilterHeavyHitters: Boolean read FFilterHeavyHitters
  57.         write FFilterHeavyHitters default True;
  58.     property FilterConsecutiveMessages: Boolean
  59.         read FFilterConsecutiveMessages
  60.         write FFilterConsecutiveMessages default False;
  61.     property Align;
  62.     property Alignment;
  63.     property AutoSize;
  64.     property Constraints;
  65.     property TabOrder;
  66.     property TabStop;
  67.     property Visible;
  68.     property OnMessageReceipt: TMessageReceiptEvent
  69.         read FOnMessageReceipt write FOnMessageReceipt;
  70.   end;
  71.  
  72. procedure Register;
  73.  
  74. implementation
  75.  
  76. type
  77.   PPointer = ^Pointer;
  78.  
  79.   EHookerError = class(Exception);
  80.  
  81.   TDispatchMethod = procedure(var Message) of Object;
  82.   TFreeInstanceMethod = procedure of Object;
  83.   TWndProcMethod = procedure(var Message: TMessage) of Object;
  84.  
  85.   TDispatchMessageEvent = procedure(const Message) of Object;
  86.   TWndProcMessageEvent = procedure(const Message: TMessage) of Object;
  87.   THookeeDestructing = TNotifyEvent;
  88.  
  89. // ---- THooker ----------------------------------------------------
  90.  
  91.   THooker = class(TControl)
  92.   private
  93.     Hookee:                 TControl;
  94.     OnDispatchMessage:      TDispatchMessageEvent;
  95.     OnWndProcMessage:       TWndProcMessageEvent;
  96.     OnHookeeDestructing:    TNotifyEvent;
  97.     ClientList:             TList;
  98.     TrueDispatchMethod:     TDispatchMethod;
  99.     TrueFreeInstanceMethod: TFreeInstanceMethod;
  100.     TrueWndProcMethod:      TWndProcMethod;
  101.     procedure DispatchHook(var Message);
  102.     function DispatchVMTAddr(AControl: TControl): Pointer;
  103.     procedure FreeInstanceHook;
  104.     function FreeInstanceVMTAddr(AControl: TControl): Pointer;
  105.     procedure HookDispatchMethod(AControl: TControl);
  106.     procedure HookFreeInstanceMethod(AControl: TControl);
  107.     procedure HookWndProcMethod(AControl: TControl);
  108.     procedure SetHookee(AControl: TControl);
  109.     procedure UnhookDispatchMethod;
  110.     procedure UnhookFreeInstanceMethod;
  111.     procedure UnhookWndProcMethod;
  112.     procedure WndProcHook(var Message: TMessage);
  113.     function WndProcIsHooked(AControl: TControl): Boolean;
  114.   public
  115.     constructor Create(AOwner: TComponent); override;
  116.     destructor Destroy; override;
  117.     class procedure AttachSpy(MS: TMessageSpy);
  118.     procedure HookControl(MS: TMessageSpy; AControl: TControl;
  119.         ParamOnWndProcMessage: TWndProcMessageEvent;
  120.         ParamOnDispatchMessage: TDispatchMessageEvent;
  121.         ParamOnHookeeDestructing: TNotifyEvent);
  122.     procedure DetachSpy(MS: TMessageSpy);
  123.     procedure UnhookControl;
  124.     procedure ViewerShowing(Showing: Boolean);
  125.   end;
  126.  
  127. var
  128.   Hooker: THooker;
  129.   MsgDict: TMessageDict;
  130.  
  131. // ---- TMessageSpy ------------------------------------------------
  132.  
  133. procedure TMessageSpy.AButtonClick(Sender: TObject);
  134. begin
  135.   Viewer := TfrmSpyViewer.Create(Self);
  136.   Viewer.OnClose := ViewerClosing;
  137.   Hooker.ViewerShowing(True);
  138.   Viewer.Show;
  139. end;
  140.  
  141. constructor TMessageSpy.Create(AOwner: TComponent);
  142. begin
  143.   inherited Create(AOwner);
  144.   FHookType := htWndProc;
  145.   FMessageTypes := [mtCM_Message, mtCN_Message];
  146.   FFilterHeavyHitters := True;
  147.   FFilterConsecutiveMessages := False;
  148.   SetBounds(Left, Top, 85, 25);
  149.   if csDesigning in ComponentState then
  150.     Caption := 'Message Spy'
  151.   else
  152.   begin
  153.     BorderStyle := bsNone;
  154.     AButton := TButton.Create(nil);
  155.     AButton.Parent := Self;
  156.     AButton.Align := alClient;
  157.     AButton.Parent := Self;
  158.     AButton.Caption := 'Message Spy';
  159.     AButton.OnClick := AButtonClick;
  160.     THooker.AttachSpy(Self);
  161.   end;
  162. end;
  163.  
  164. destructor TMessageSpy.Destroy;
  165. begin
  166.   if Viewer <> nil then
  167.     Viewer.btnClose.Click;
  168.   if Hooker <> nil then
  169.     Hooker.DetachSpy(Self);
  170.   AButton.Free;
  171.   inherited Destroy;
  172. end;
  173.  
  174. procedure TMessageSpy.DispatchMessage(const Message);
  175. var
  176.   FMT: TFormattedMsgType;
  177. begin
  178.   if (FHookType <> htWndProc) and DisplayMessage(htDispatch, Message) then
  179.   begin
  180.     if FHookType = htBoth then
  181.       FMT := fmtDispatch
  182.     else
  183.       FMT := fmtNotApplicable;
  184.     Viewer.AddLine(FormattedMessage(FMT, Message));
  185.   end;
  186. end;
  187.  
  188. function TMessageSpy.DisplayMessage(HT: THookType; const Message): Boolean;
  189. var
  190.   Msg:  Word;
  191.   Filter: Boolean;
  192. begin
  193.   Msg := Word(Message);
  194.   Result := ((mtWindowsMessage in FMessageTypes) and (Msg < CM_BASE)) or
  195.       ((mtCM_Message in FMessageTypes) and
  196.           (Msg >= CM_Base) and (Msg < CN_Base)) or
  197.       ((mtCN_Message in FMessageTypes) and
  198.           (Msg >= CN_BASE));
  199.   if not Result then
  200.     Exit;
  201.   if FFilterHeavyHitters then
  202.     Result := not ((Msg = WM_NCHITTEST) or
  203.         (Msg = WM_SETCURSOR) or
  204.         (Msg = WM_MOUSEMOVE) or
  205.         (Msg = CM_HITTEST));
  206.   if Result and FFilterConsecutiveMessages then
  207.     Result := not DuplicateMessage(HT, Message);
  208.   if Result and Assigned(FOnMessageReceipt) then
  209.   begin
  210.     Filter := False;
  211.     FOnMessageReceipt(Hookee, TMessage(Message), HT,
  212.         Viewer.reMessages.Lines, Filter);
  213.     Result := not Filter;
  214.   end;
  215. end;
  216.  
  217. function TMessageSpy.DuplicateMessage(HT: THookType; const Message): Boolean;
  218. var
  219.   Msg: Word;
  220. begin
  221.   Msg := Word(Message);
  222.   if HT = htDispatch then
  223.   begin
  224.     if Msg < CM_BASE then
  225.     begin
  226.       Result := Msg = LastMsgDWM;
  227.       LastMsgDWM := Msg;
  228.     end
  229.     else if Msg >= CN_BASE then
  230.     begin
  231.       Result := Msg = LastMsgDCN;
  232.       LastMsgDCN := Msg;
  233.     end
  234.     else
  235.     begin
  236.       Result := Msg = LastMsgDCM;
  237.       LastMsgDCM := Msg;
  238.     end;
  239.   end
  240.   else
  241.   begin
  242.     if Msg < CM_BASE then
  243.     begin
  244.       Result := Msg = LastMsgWPWM;
  245.       LastMsgWPWM := Msg;
  246.     end
  247.     else if Msg >= CN_BASE then
  248.     begin
  249.       Result := Msg = LastMsgWPCN;
  250.       LastMsgWPCN := Msg;
  251.     end
  252.     else
  253.     begin
  254.       Result := Msg = LastMsgWPCM;
  255.       LastMsgWPCM := Msg;
  256.     end;
  257.   end;
  258. end;
  259.  
  260. function TMessageSpy.FormattedMessage(MsgType: TFormattedMsgType;
  261.     const Msg): String;
  262. var
  263.   Prfx: String[2];
  264. begin
  265.   case MsgType of
  266.     fmtDispatch: Prfx := 'D ';
  267.     fmtWndProc:  Prfx := 'W ';
  268.   else
  269.     Prfx := '';
  270.   end;
  271.   Result := Prfx +
  272.       MsgDict.MessageName(TMessage(Msg).Msg) + ' ' +
  273.       IntToHex(TMessage(Msg).WParamHi, 4) + ' ' +
  274.       IntToHex(TMessage(Msg).WParamLo, 4) + ' ' +
  275.       IntToHex(TMessage(Msg).LParamHi, 4) + ' ' +
  276.       IntToHex(TMessage(Msg).LParamLo, 4) + ' ';
  277. end;
  278.  
  279. procedure TMessageSpy.HookeeDestructing(Sender: TObject);
  280. begin
  281.   Hooker.UnhookControl;
  282.   Hookee := nil;
  283.   Viewer.Spying := False;
  284. end;
  285.  
  286. procedure TMessageSpy.HookerEngaged(Engaged: Boolean);
  287. begin
  288.   AButton.Enabled := not Engaged;
  289. end;
  290.  
  291. procedure TMessageSpy.SetHookActive(Active: Boolean);
  292. begin
  293.   if Active then
  294.     Hooker.HookControl(Self, FHookee, WndProcMessage, DispatchMessage,
  295.       HookeeDestructing)
  296.   else
  297.     Hooker.UnhookControl;
  298.   LastMsgDWM := $FFFF;
  299.   LastMsgDCM := $FFFF;
  300.   LastMsgDCN := $FFFF;
  301.   LastMsgWPWM := $FFFF;
  302.   LastMsgWPCM := $FFFF;
  303.   LastMsgWPCN := $FFFF;
  304. end;
  305.  
  306. procedure TMessageSpy.ViewerClosing(Sender: TObject;
  307.   var Action: TCloseAction);
  308. begin
  309.   Hooker.ViewerShowing(False);
  310.   SetHookActive(False);
  311.   Action := caFree;
  312.   Viewer := nil;
  313. end;
  314.  
  315. procedure TMessageSpy.WndProcMessage(const Message: TMessage);
  316. var
  317.   FMT: TFormattedMsgType;
  318. begin
  319.   if (FHookType <> htDispatch) and DisplayMessage(htWndProc, Message) then
  320.   begin
  321.     if FHookType = htBoth then
  322.       FMT := fmtWndProc
  323.     else
  324.       FMT := fmtNotApplicable;
  325.     Viewer.AddLine(FormattedMessage(FMT, Message));
  326.   end;
  327. end;
  328.  
  329. // ---- THooker ----------------------------------------------------
  330.  
  331. class procedure THooker.AttachSpy(MS: TMessageSpy);
  332. begin
  333.   if Hooker = nil then
  334.   begin
  335.     Hooker := THooker.Create(nil);
  336.     MsgDict := TMessageDict.Create;
  337.   end;
  338.   Hooker.ClientList.Add(MS);
  339. end;
  340.  
  341. constructor THooker.Create(AOwner: TComponent);
  342. begin
  343.   inherited Create(AOwner);
  344.   ClientList := TList.Create;
  345. end;
  346.  
  347. destructor THooker.Destroy;
  348. begin
  349.   ClientList.Free;
  350.   inherited Destroy;
  351. end;
  352.  
  353. procedure THooker.DetachSpy(MS: TMessageSpy);
  354. begin
  355.   ClientList.Remove(MS);
  356. end;
  357.  
  358. procedure THooker.DispatchHook(var Message);
  359. begin
  360.   // if Self is the Hookee then fire the dispatch message event
  361.   if (Hooker.Hookee = Self) and
  362.       Assigned(Hooker.OnDispatchMessage) then
  363.     Hooker.OnDispatchMessage(Message);
  364.   // set the true dispatch method's object reference to the "self"
  365.   // passed in to this method
  366.   TMethod(Hooker.TrueDispatchMethod).Data := Self;
  367.   Hooker.TrueDispatchMethod(Message);
  368. end;
  369.  
  370. function THooker.DispatchVMTAddr(AControl: TControl): Pointer;
  371. begin
  372.   // get address of AControl's class's MVT
  373.   Result := Pointer(Pointer(AControl)^);
  374.   // subract offset of Dispatch to the pointer
  375.   Inc(PChar(Result), vmtDispatch);
  376. end;
  377.  
  378. procedure THooker.FreeInstanceHook;
  379. begin
  380.   // if Self is the Hookee, then the Hookee is in the process of
  381.   // being destroyed; in such a case, notify the Hooker that it
  382.   // must release its control over the Hookee
  383.   if Hooker.Hookee = Self then
  384.     Hooker.OnHookeeDestructing(Self);
  385.   TMethod(Hooker.TrueFreeInstanceMethod).Data := Self;
  386.   Hooker.TrueFreeInstanceMethod;
  387. end;
  388.  
  389. function THooker.FreeInstanceVMTAddr(AControl: TControl): Pointer;
  390. begin
  391.   // get address of AControl's class's MVT
  392.   Result := Pointer(Pointer(AControl)^);
  393.   // subract offset of Dispatch to the pointer
  394.   Inc(PChar(Result), vmtFreeInstance);
  395. end;
  396.  
  397. procedure THooker.HookControl(MS: TMessageSpy; AControl: TControl;
  398.     ParamOnWndProcMessage: TWndProcMessageEvent;
  399.     ParamOnDispatchMessage: TDispatchMessageEvent;
  400.     ParamOnHookeeDestructing: TNotifyEvent);
  401. begin
  402.   if ClientList.IndexOf(MS) = -1 then
  403.     raise EHookerError.Create('HookControl requested by ' +
  404.         'unattached TMessageSpy');
  405.   if Hookee <> nil then
  406.     raise EHookerError.Create('Multiple control hooks requested');
  407.   SetHookee(AControl);
  408.   OnWndProcMessage := ParamOnWndProcMessage;
  409.   OnDispatchMessage := ParamOnDispatchMessage;
  410.   OnHookeeDestructing := ParamOnHookeeDestructing;
  411. end;
  412.  
  413. procedure THooker.HookDispatchMethod(AControl: TControl);
  414. var
  415.   P:    Pointer;
  416.   M:    TMethod;
  417.   Cnt:  Cardinal;
  418. begin
  419.   // set P to the control's class's VMT address of Dispatch
  420.   P := DispatchVMTAddr(AControl);
  421.   // save it in TrueDispatchMethod
  422.   TMethod(TrueDispatchMethod).Code := Pointer(P^);
  423.   // set the VMT addr of the control's class's MVT Dispatch address
  424.   // to that of our own Dispatch
  425.   TDispatchMethod(M) := DispatchHook;
  426.   WriteProcessMemory(GetCurrentProcess, P, @M.Code,
  427.       SizeOf(Pointer), Cnt);
  428. end;
  429.  
  430. procedure THooker.HookFreeInstanceMethod(AControl: TControl);
  431. var
  432.   P:    Pointer;
  433.   M:    TMethod;
  434.   Cnt:  Cardinal;
  435. begin
  436.   // set P to the control's class's VMT address of Dispatch
  437.   P := FreeInstanceVMTAddr(AControl);
  438.   // save it in TrueFreeInstanceMethod
  439.   TMethod(TrueFreeInstanceMethod).Code := Pointer(P^);
  440.   // set the VMT addr of the control's class's MVT FreeInstance address
  441.   // to that of our own FreeInstance
  442.   TFreeInstanceMethod(M) := FreeInstanceHook;
  443.   WriteProcessMemory(GetCurrentProcess, P, @M.Code,
  444.       SizeOf(Pointer), Cnt);
  445. end;
  446.  
  447. procedure THooker.HookWndProcMethod(AControl: TControl);
  448. begin
  449.   if WndProcIsHooked(AControl) then
  450.     raise EHookerError.Create('Cannot attach to control; ' +
  451.         'the control currently has a WindowProc hook active');
  452.   TrueWndProcMethod := AControl.WindowProc;
  453.   AControl.WindowProc := WndProcHook;
  454. end;
  455.  
  456. procedure THooker.SetHookee(AControl: TControl);
  457. begin
  458.   if AControl <> Hookee then
  459.   begin
  460.     if Hookee <> nil then
  461.     begin
  462.       UnhookFreeInstanceMethod;
  463.       UnhookDispatchMethod;
  464.       UnhookWndProcMethod;
  465.     end
  466.     else
  467.     begin
  468.       HookWndProcMethod(AControl);
  469.       HookDispatchMethod(AControl);
  470.       HookFreeInstanceMethod(AControl);
  471.     end;
  472.     Hooker := Self;
  473.     Hookee := AControl;
  474.   end;
  475. end;
  476.  
  477. procedure THooker.UnhookControl;
  478. begin
  479.   if Hookee <> nil then
  480.     SetHookee(nil);
  481. end;
  482.  
  483. procedure THooker.UnhookDispatchMethod;
  484. var
  485.   P:    Pointer;
  486.   M:    TMethod;
  487.   Cnt:  Cardinal;
  488. begin
  489.   // set P to the control's class's VMT address of Dispatch
  490.   P := DispatchVMTAddr(Hookee);
  491.   // restore the true Dispatch method address in that location
  492.   M := TMethod(TrueDispatchMethod);
  493.   WriteProcessMemory(GetCurrentProcess, P, @M.Code,
  494.       SizeOf(Pointer), Cnt);
  495. end;
  496.  
  497. procedure THooker.UnhookFreeInstanceMethod;
  498. var
  499.   P:    Pointer;
  500.   M:    TMethod;
  501.   Cnt:  Cardinal;
  502. begin
  503.   // set P to the control's class's VMT address of FreeInstance
  504.   P := FreeInstanceVMTAddr(Hookee);
  505.   // restore the true Dispatch method address in that location
  506.   M := TMethod(TrueFreeInstanceMethod);
  507.   WriteProcessMemory(GetCurrentProcess, P, @M.Code,
  508.       SizeOf(Pointer), Cnt);
  509. end;
  510.  
  511. procedure THooker.UnhookWndProcMethod;
  512. begin
  513.   Hookee.WindowProc := TrueWndProcMethod;
  514. end;
  515.  
  516. procedure THooker.ViewerShowing(Showing: Boolean);
  517. var
  518.   I: Integer;
  519. begin
  520.   for I := 0 to ClientList.Count - 1 do
  521.     TMessageSpy(ClientList[I]).HookerEngaged(Showing);
  522. end;
  523.  
  524. procedure THooker.WndProcHook(var Message: TMessage);
  525. begin
  526.   // fire the wndproc message event
  527.   if Assigned(Hooker.OnWndProcMessage) then
  528.     Hooker.OnWndProcMessage(Message);
  529.   Hooker.TrueWndProcMethod(Message);
  530. end;
  531.  
  532. function THooker.WndProcIsHooked(AControl: TControl): Boolean;
  533. var
  534.   P: PPointer;
  535.   WPPosition: Integer;
  536.   WPMethod: TWndProcMethod;
  537. begin
  538.   // get address of our class's WndProc method by assigning it to
  539.   // the event variable; the first 4 bytes of this is the address
  540.   // we need to find in our class's VMT
  541.   WPMethod := WndProc;
  542.   // get the address of our class's VMT
  543.   P := Pointer(Pointer(Self)^);
  544.   // interate through the VMT until we find the entry that equals
  545.   // that of our WndProc
  546.   while Pointer(TMethod(WPMethod).Code) <> Pointer(P^) do
  547.     Inc(P);
  548.   // the offset result is the address at which our WndProc was found
  549.   // minus the start of our VMT
  550.   WPPosition := (PChar(P) - PChar(Pointer(Self)^)) div 4;
  551.   P := Pointer(Pointer(AControl)^);
  552.   // add offset of WndProc to the pointer; the offset of WndProc will
  553.   // be the same for all TControl derived classes
  554.   Inc(P, WPPosition);
  555.   // finally, check to see if AControl's WindowProc property does not
  556.   // equal that WndProc address in AControl's VMT; if it does not then
  557.   // the control has a WndProc hook active
  558.   Result := (TMethod(AControl.WindowProc).Code <> P^);
  559. end;
  560.  
  561. // ---- Register ---------------------------------------------------
  562.  
  563. procedure Register;
  564. begin
  565.   RegisterComponents('Samples', [TMessageSpy]);
  566. end;
  567.  
  568. initialization
  569.  
  570. finalization
  571.  
  572.   Hooker.Free;
  573.   MsgDict.Free;
  574.  
  575. end.
  576.